package com.sukaiyi.giteeideaplugin.mainwindow.repodetailtabbedpane;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.ui.EditorTextField;
import com.intellij.ui.JBColor;
import com.intellij.ui.JBSplitter;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.ui.AsyncProcessIcon;
import com.sukaiyi.giteeideaplugin.cellrenderer.GitFileTreeCellRenderer;
import com.sukaiyi.giteeideaplugin.cellrenderer.RepoBranchListCellRenderer;
import com.sukaiyi.giteeideaplugin.model.GitFileNode;
import com.sukaiyi.giteeideaplugin.utils.FileTypeUtils;
import com.sukaiyi.giteeideaplugin.utils.IdeaUIUtils;
import com.sukaiyi.giteeideaplugin.utils.NotificationUtils;
import com.sukaiyi.giteeideaplugin.utils.ProjectUtils;
import com.sukaiyi.giteejavaapi.common.Gitee;
import com.sukaiyi.giteejavaapi.common.Params;
import com.sukaiyi.giteejavaapi.http.model.HttpResponse;
import icons.PluginIcons;

import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

/**
 * @author sukaiyi
 * @date 2020/07/06
 */
public class CodeTabbedPane implements RepoDetailPane {

    private static final Logger logger = Logger.getInstance(CodeTabbedPane.class);

    private final JSONObject repo;

    private JPanel content;

    private JBLabel descriptionLabel;
    private JComboBox<JSONObject> branchComboBox;
    private AsyncProcessIcon codeLoadingIcon;
    private JBLabel commitMessageLabel;

    private JBSplitter treeCodeSplitter;
    private Tree fileTree;
    private EditorTextField codeTextField;

    public CodeTabbedPane(JSONObject repo) {
        this.repo = repo;
        content.add(treeCodeSplitter);
        branchComboBox.setRenderer(new RepoBranchListCellRenderer());
        branchComboBox.addItemListener(itemEvent -> {
            if (itemEvent.getStateChange() != ItemEvent.SELECTED) {
                return;
            }
            codeLoadingIcon.resume();
            codeLoadingIcon.setVisible(true);
            Object obj = itemEvent.getItem();
            JSONObject branchItem = (JSONObject) obj;
            String branch = branchItem.getStr("name");
            CompletableFuture.supplyAsync(() -> this.getRepoGitTree(branch))
                    .thenApply(this::parseRepoGitTree) // 解析文件成树
                    .thenApply(this::toTreeNode) // 转换成 Tree 控件所需的树节点
                    .thenAccept(this::updateGitFileTree) // 更新文件 Tree 控件
                    .whenComplete((ret, ex) -> {
                        codeLoadingIcon.suspend();
                        codeLoadingIcon.setVisible(false);
                        if (ex != null) {
                            throw ExceptionUtil.wrapRuntime(ex);
                        }
                    });
            CompletableFuture.supplyAsync(() -> this.getBranchInfo(branch))
                    .thenAccept(this::updateCommitMessageLabel)
                    .whenComplete((ret, ex) -> {

                    });
        });
        fileTree.addTreeSelectionListener(treeSelectionEvent -> {
            GitFileNode node = Optional.of(treeSelectionEvent)
                    .map(TreeSelectionEvent::getNewLeadSelectionPath)
                    .map(TreePath::getLastPathComponent)
                    .filter(t -> t instanceof DefaultMutableTreeNode)
                    .map(t -> (DefaultMutableTreeNode) t)
                    .map(DefaultMutableTreeNode::getUserObject)
                    .filter(t -> t instanceof GitFileNode)
                    .map(t -> (GitFileNode) t)
                    .filter(t -> Objects.equals("blob", t.getType()))
                    .orElse(null);
            if (node == null) {
                return;
            }

            codeLoadingIcon.setVisible(true);
            codeLoadingIcon.resume();

            Params params = Params.create()
                    .set("owner", repo.getByPath("namespace.path", String.class))
                    .set("repo", repo.getStr("path"))
                    .set("sha", node.getSha());
            Gitee.getInstance().invoke("getV5ReposOwnerRepoGitBlobsSha", params)
                    .thenApply(HttpResponse::asJSONObject)
                    .thenApply(res -> {
                        String contentBase64Encoded = res.getStr("content");
                        String contentBase64Decoded = Base64.decodeStr(contentBase64Encoded);
                        res.put("content", contentBase64Decoded);
                        return res;
                    })
                    .thenAccept(res -> {
                        String content = res.getStr("content", "");
                        SwingUtilities.invokeLater(() -> {
                            codeTextField = IdeaUIUtils.createEditorTextField(ProjectUtils.getProject(), true, FileTypeUtils.getFileTypeForName(node.getPath()));
                            codeTextField.setPreferredSize(new Dimension(400, 300));
                            Document document = EditorFactory.getInstance().createDocument(content.replace("\r\n", "\n"));
                            codeTextField.setDocument(document);
                            codeTextField.setCaretPosition(0);
                            codeTextField.setBorder(BorderFactory.createEmptyBorder());
                            treeCodeSplitter.setSecondComponent(codeTextField);
                        });
                    })
                    .whenComplete((res, ex) -> {
                        codeLoadingIcon.setVisible(false);
                        codeLoadingIcon.suspend();
                        if (ex != null) {
                            logger.error(ex);
                            NotificationUtils.notify(ex.getMessage());
                        }
                    });
        });

        String description = repo.getStr("description");
        if (StrUtil.isEmpty(description)) {
            descriptionLabel.setText("暂无描述...");
            descriptionLabel.setForeground(JBColor.GRAY);
        } else {
            descriptionLabel.setText(String.format("<html>%s</html>", description));
        }
        refreshBranch();
    }

    public void createUIComponents() {
        fileTree = new Tree();
        fileTree.setCellRenderer(new GitFileTreeCellRenderer());
        treeCodeSplitter = new JBSplitter();
        treeCodeSplitter.setSplitterProportionKey(this.getClass().getName() + "#treeCodeSplitter");
        treeCodeSplitter.setDividerWidth(10);
        treeCodeSplitter.setProportion(0.3f);
        treeCodeSplitter.setFirstComponent(new JBScrollPane(fileTree));
        treeCodeSplitter.setSecondComponent(new JBLabel());
        codeLoadingIcon = new AsyncProcessIcon("loading");
    }

    private void refreshBranch() {
        Params params = Params.create()
                .set("owner", (String) repo.getByPath("namespace.path"))
                .set("repo", repo.getStr("path"));
        Gitee.getInstance().invoke("getV5ReposOwnerRepoBranches", params) // 拉取分支
                .thenApply(HttpResponse::asJSONArray) // 结果转换成分支数组
                .thenApply(this::acceptBranchesAndReturnDefaultBranch) // 更新分支下拉列表，并返回默认分支
                .whenComplete((ret, ex) -> {
                    if (ex != null) {
                        NotificationUtils.notify(ex.getMessage());
                    }
                });
    }

    private String acceptBranchesAndReturnDefaultBranch(JSONArray branches) {
        // 确保默认分支第一个加到 branchComboBox 组件中
        String defaultBranch = repo.getStr("default_branch", "master");
        JSONObject defaultBranchItem = null;
        List<JSONObject> beforeDefaultBranches = new ArrayList<>();
        for (int i = 0; i < branches.size(); i++) {
            JSONObject item = branches.getJSONObject(i);
            if (Objects.equals(defaultBranch, item.getStr("name"))) {
                defaultBranchItem = item;
            }
            if (defaultBranchItem != null) {
                branchComboBox.addItem(item);
            } else {
                beforeDefaultBranches.add(item);
            }
        }
        beforeDefaultBranches.forEach(branchComboBox::addItem);
        return defaultBranch;
    }

    private JSONObject getBranchInfo(String branch) {
        Params params = Params.create()
                .set("owner", repo.getByPath("namespace.path", String.class))
                .set("repo", repo.getStr("path"))
                .set("branch", branch);
        try {
            HttpResponse response = Gitee.getInstance().invoke("getV5ReposOwnerRepoBranchesBranch", params).get();
            return response.asJSONObject();
        } catch (InterruptedException | ExecutionException e) {
            throw ExceptionUtil.wrapRuntime(e);
        }
    }

    private void updateCommitMessageLabel(JSONObject branchInfo) {
        String message = branchInfo.getByPath("commit.commit.message", String.class);
        String author = branchInfo.getByPath("commit.commit.author.name", String.class);
        commitMessageLabel.setVisible(true);
        commitMessageLabel.setIcon(PluginIcons.FORK);
        commitMessageLabel.setText(String.format("%s: %s", author, message));
    }

    private JSONObject getRepoGitTree(String branch) {
        Params params = Params.create()
                .set("owner", repo.getByPath("namespace.path", String.class))
                .set("repo", repo.getStr("path"))
                .set("sha", branch)
                .set("recursive", "1");
        try {
            HttpResponse response = Gitee.getInstance().invoke("getV5ReposOwnerRepoGitTreesSha", params).get();
            return response.asJSONObject();
        } catch (InterruptedException | ExecutionException e) {
            throw ExceptionUtil.wrapRuntime(e);
        }
    }

    private GitFileNode parseRepoGitTree(JSONObject treeObj) {
        JSONArray arr = treeObj.getJSONArray("tree");
        Comparator<JSONObject> comparator = Comparator.comparing(obj -> obj.getStr("type"));
        comparator = comparator.reversed();
        comparator = comparator.thenComparing(obj -> obj.getStr("path"));
        List<JSONObject> objs = arr.stream()
                .map(obj -> {
                    if (obj instanceof JSONObject) {
                        return (JSONObject) obj;
                    }
                    return new JSONObject(obj);
                })
                .sorted(comparator)
                .collect(Collectors.toList());
        Map<String, GitFileNode> fileNameMap = new LinkedHashMap<>();
        List<GitFileNode> rootNodes = new ArrayList<>();
        for (JSONObject obj : objs) {
            GitFileNode node = obj.toBean(GitFileNode.class);
            String nodeName = node.getPath();
            fileNameMap.put(nodeName, node);
            if (!nodeName.contains("/")) {
                rootNodes.add(node);
            }
        }
        for (Map.Entry<String, GitFileNode> entry : fileNameMap.entrySet()) {
            String name = entry.getKey();
            GitFileNode node = entry.getValue();
            int last = name.lastIndexOf('/');
            if (last == -1) {
                continue;
            }
            String pName = name.substring(0, last);
            node.setPath(name.substring(last + 1));
            GitFileNode pNode = fileNameMap.get(pName);
            if (pNode != null) {
                List<GitFileNode> children = pNode.getChildren();
                if (children == null) {
                    children = new ArrayList<>();
                    pNode.setChildren(children);
                }
                children.add(node);
            }
        }
        return new GitFileNode(repo.getStr("name"), null, null, null, null, null, rootNodes);
    }

    private MutableTreeNode toTreeNode(GitFileNode fileNode) {
        DefaultMutableTreeNode node = new DefaultMutableTreeNode(fileNode);
        List<GitFileNode> children = fileNode.getChildren();
        if (children != null && children.size() > 0) {
            for (GitFileNode child : children) {
                node.add(toTreeNode(child));
            }
        }
        return node;
    }

    private void updateGitFileTree(TreeNode treeNode) {
        SwingUtilities.invokeLater(() -> fileTree.setModel(new DefaultTreeModel(treeNode)));
    }

    @Override
    public Icon getIcon() {
        return PluginIcons.SHELF;
    }

    @Override
    public String getTitle() {
        return "代码";
    }

    @Override
    public int getIndex() {
        return 0;
    }
}
